package db

import (
	"database/sql"
	"fmt"
	"golang.org/x/crypto/ssh"
	"io/ioutil"
	"time"
)

// Node represents a node in the database
type Node struct {
	ID          int       `json:"id"`
	Name        string    `json:"name"`
	Address     string    `json:"address"`
	SSHPort     int       `json:"ssh_port"`
	SSHUser     string    `json:"ssh_user"`
	SSHKeyPath  string    `json:"ssh_key_path,omitempty"`
	SSHPassword string    `json:"ssh_password,omitempty"`
	RemoteDir   string    `json:"remote_dir"`
	Description string    `json:"description"`
	Bootstraped bool      `json:"bootstraped"` // Whether the node has been bootstrapped
	TrodePort   int       `json:"trode_port,omitempty"` // Port where trode is running
	TrodeService string   `json:"trode_service,omitempty"` // Systemd service name
	TrodeDeployed bool    `json:"trode_deployed"` // Whether trode is deployed
	CreatedAt   time.Time `json:"created_at"`
	UpdatedAt   time.Time `json:"updated_at"`
}

// SSHValidationResult represents the result of SSH validation
type SSHValidationResult struct {
	Valid     bool   `json:"valid"`
	HasSudo   bool   `json:"has_sudo"`
	Message   string `json:"message"`
	Error     string `json:"error,omitempty"`
}

// BootstrapResult represents the result of the bootstrap process
type BootstrapResult struct {
	Success     bool   `json:"success"`
	PublicKey   string `json:"public_key,omitempty"`
	PrivateKey  string `json:"private_key,omitempty"`
	KeyPath     string `json:"key_path,omitempty"`
	Message     string `json:"message"`
	Error       string `json:"error,omitempty"`
}

// InsertNode inserts a new node into the database
func (db *DB) InsertNode(node *Node) error {
	result, err := db.Exec(`
		INSERT INTO nodes (name, address, ssh_port, ssh_user, ssh_key_path, ssh_password, 
		                  remote_dir, description, bootstraped, trode_port, trode_service, trode_deployed)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
	`, node.Name, node.Address, node.SSHPort, node.SSHUser, node.SSHKeyPath, node.SSHPassword, 
	   node.RemoteDir, node.Description, node.Bootstraped, node.TrodePort, node.TrodeService, node.TrodeDeployed)
	if err != nil {
		return err
	}

	id, err := result.LastInsertId()
	if err != nil {
		return err
	}

	node.ID = int(id)
	return nil
}

// UpdateNode updates an existing node in the database
func (db *DB) UpdateNode(node *Node) error {
	_, err := db.Exec(`
		UPDATE nodes 
		SET name = ?, address = ?, ssh_port = ?, ssh_user = ?, ssh_key_path = ?, 
			ssh_password = ?, remote_dir = ?, description = ?, bootstraped = ?, 
			trode_port = ?, trode_service = ?, trode_deployed = ?, updated_at = CURRENT_TIMESTAMP
		WHERE id = ?
	`, node.Name, node.Address, node.SSHPort, node.SSHUser, node.SSHKeyPath,
		node.SSHPassword, node.RemoteDir, node.Description, node.Bootstraped,
		node.TrodePort, node.TrodeService, node.TrodeDeployed, node.ID)
	return err
}

// UpdateNodeBootstrapStatus updates the bootstrap status of a node
func (db *DB) UpdateNodeBootstrapStatus(id int, bootstraped bool, keyPath string) error {
	_, err := db.Exec(`
		UPDATE nodes 
		SET bootstraped = ?, ssh_key_path = ?, ssh_password = '', updated_at = CURRENT_TIMESTAMP
		WHERE id = ?
	`, bootstraped, keyPath, id)
	return err
}

// UpdateNodeTrodeInfo updates the trode deployment information for a node
func (db *DB) UpdateNodeTrodeInfo(id int, trodePort int, trodeService string) error {
	_, err := db.Exec(`
		UPDATE nodes 
		SET trode_port = ?, trode_service = ?, trode_deployed = true, updated_at = CURRENT_TIMESTAMP
		WHERE id = ?
	`, trodePort, trodeService, id)
	return err
}

// GetNode retrieves a node by ID
func (db *DB) GetNode(id int) (*Node, error) {
	row := db.QueryRow(`
		SELECT id, name, address, ssh_port, ssh_user, ssh_key_path, ssh_password, 
		       remote_dir, description, bootstraped, trode_port, trode_service, trode_deployed,
		       created_at, updated_at
		FROM nodes WHERE id = ?
	`, id)

	node := &Node{}
	err := row.Scan(&node.ID, &node.Name, &node.Address, &node.SSHPort, &node.SSHUser,
		&node.SSHKeyPath, &node.SSHPassword, &node.RemoteDir, &node.Description,
		&node.Bootstraped, &node.TrodePort, &node.TrodeService, &node.TrodeDeployed,
		&node.CreatedAt, &node.UpdatedAt)
	if err != nil {
		return nil, err
	}

	return node, nil
}

// GetNodeByName retrieves a node by name
func (db *DB) GetNodeByName(name string) (*Node, error) {
	row := db.QueryRow(`
		SELECT id, name, address, ssh_port, ssh_user, ssh_key_path, ssh_password, 
		       remote_dir, description, bootstraped, trode_port, trode_service, trode_deployed,
		       created_at, updated_at
		FROM nodes WHERE name = ?
	`, name)

	node := &Node{}
	err := row.Scan(&node.ID, &node.Name, &node.Address, &node.SSHPort, &node.SSHUser,
		&node.SSHKeyPath, &node.SSHPassword, &node.RemoteDir, &node.Description,
		&node.Bootstraped, &node.TrodePort, &node.TrodeService, &node.TrodeDeployed,
		&node.CreatedAt, &node.UpdatedAt)
	if err != nil {
		return nil, err
	}

	return node, nil
}

// GetAllNodes retrieves all nodes from the database
func (db *DB) GetAllNodes() ([]*Node, error) {
	rows, err := db.Query(`
		SELECT id, name, address, ssh_port, ssh_user, ssh_key_path, ssh_password, 
		       remote_dir, description, bootstraped, trode_port, trode_service, trode_deployed,
		       created_at, updated_at
		FROM nodes ORDER BY name
	`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var nodes []*Node
	for rows.Next() {
		node := &Node{}
		err := rows.Scan(&node.ID, &node.Name, &node.Address, &node.SSHPort, &node.SSHUser,
			&node.SSHKeyPath, &node.SSHPassword, &node.RemoteDir, &node.Description,
			&node.Bootstraped, &node.TrodePort, &node.TrodeService, &node.TrodeDeployed,
			&node.CreatedAt, &node.UpdatedAt)
		if err != nil {
			return nil, err
		}
		nodes = append(nodes, node)
	}

	return nodes, rows.Err()
}

// DeleteNode deletes a node by ID
func (db *DB) DeleteNode(id int) error {
	_, err := db.Exec("DELETE FROM nodes WHERE id = ?", id)
	return err
}

// NodeExists checks if a node with the given name exists
func (db *DB) NodeExists(name string) (bool, error) {
	var exists int
	err := db.QueryRow("SELECT 1 FROM nodes WHERE name = ?", name).Scan(&exists)
	if err == sql.ErrNoRows {
		return false, nil
	}
	if err != nil {
		return false, err
	}
	return true, nil
}

// ValidateSSHCredentials validates SSH credentials and checks sudo access
func (db *DB) ValidateSSHCredentials(node *Node) (*SSHValidationResult, error) {
	result := &SSHValidationResult{}

	// Create SSH client config
	config := &ssh.ClientConfig{
		User: node.SSHUser,
		Auth: []ssh.AuthMethod{},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
		Timeout:         10 * time.Second,
	}

	// Add authentication method
	if node.SSHKeyPath != "" {
		// Use SSH key authentication
		key, err := ioutil.ReadFile(node.SSHKeyPath)
		if err != nil {
			result.Error = fmt.Sprintf("Failed to read SSH key: %v", err)
			return result, nil
		}

		signer, err := ssh.ParsePrivateKey(key)
		if err != nil {
			result.Error = fmt.Sprintf("Failed to parse SSH key: %v", err)
			return result, nil
		}

		config.Auth = append(config.Auth, ssh.PublicKeys(signer))
	} else if node.SSHPassword != "" {
		// Use password authentication
		config.Auth = append(config.Auth, ssh.Password(node.SSHPassword))
	} else {
		result.Error = "No SSH key path or password provided"
		return result, nil
	}

	// Connect to the remote host
	client, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", node.Address, node.SSHPort), config)
	if err != nil {
		result.Error = fmt.Sprintf("SSH connection failed: %v", err)
		return result, nil
	}
	defer client.Close()

	// Test basic connection
	result.Valid = true
	result.Message = "SSH connection successful"

	// Check sudo access
	session, err := client.NewSession()
	if err != nil {
		result.Error = fmt.Sprintf("Failed to create SSH session: %v", err)
		return result, nil
	}
	defer session.Close()

	// Test sudo access by running a simple command
	err = session.Run("sudo -n true")
	if err == nil {
		result.HasSudo = true
		result.Message += ", sudo access confirmed"
	} else {
		result.HasSudo = false
		result.Message += ", no sudo access (password required)"
	}

	return result, nil
}
